#include <GL/glew.h>
#include "core/exceptions.h"
#include "core/draw/VertexBuffer.h"

namespace core
{

   namespace draw
   {

    
      VertexBuffer::VertexBuffer(u32 size, Type type)
      {
	 usingVBO = GLEW_ARB_vertex_buffer_object;
	 //usingVBO = false;
	 this->size = size;
	 this->stride = calcStride(type);
	 this->type = type;

	 this->locked = false;
      
	 if (usingVBO){
	    glGenBuffersARB(1, &vboId);
	    data = 0;
				
	    vboUsage = GL_STREAM_DRAW_ARB;
	    vboAccess = GL_WRITE_ONLY_ARB;
				
	    glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
	    glBufferDataARB(GL_ARRAY_BUFFER_ARB, size * stride, NULL, vboUsage);
	    unbind();
	 }
	 else{
	    data = static_cast<void*>(new char[size * stride]);
	 }
      }

      VertexBuffer::~VertexBuffer()
      {
	 if (usingVBO) {
	    glDeleteBuffersARB(1, &vboId);
	 } 
	 else {
	    delete [] static_cast<char*>(data);
	 }
      }

      void* VertexBuffer::lock()
      {
	 if (locked){
	    throw InvalidOperationException("VB: locking already locked buffer");
	 }
      
	 void* ret = data;
      
	 if (usingVBO){
	    //glGetError();
	    //bind();

	    glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
	    ret = glMapBufferARB(GL_ARRAY_BUFFER_ARB, vboAccess);
	    if (glGetError() != GL_NO_ERROR){
	       throw InvalidOperationException("VertexBuffer: VBO Mapping failed");
	    }
				
	
	 }

	 locked = true;
	 return ret;
      }

      void VertexBuffer::unlock()
      {
	 if (!locked){
	    throw InvalidOperationException("VB: unlocking not-locked buffer");
	 }


	 locked = false;
      
	 if (usingVBO){
	    glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
	    unbind();
	 }
      }

      void VertexBuffer::bind()
      {
	 if (usingVBO){
	    glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
	 }
	 const char* ptr = static_cast<const char*>(data);
	 /*const char* vptr = ptr + offset(Vertex);
	 const char* tptr = ptr + offset((Type)(1 << TexShift));

	 const char* scptr = ptr + offset(SecColor);
	 const char* fptr = ptr + offset(FogCoord);*/

	 const char* vptr = ptr + offset(Vertex);
	 glVertexPointer(3, GL_FLOAT, stride, vptr );
	 glEnable(GL_VERTEX_ARRAY);
	 glEnableClientState(GL_VERTEX_ARRAY);
	 if (type & Normal) {
	    const char* nptr = ptr + offset(Normal);
	    glNormalPointer(GL_FLOAT, stride, nptr);
	    glEnableClientState(GL_NORMAL_ARRAY);
	 }
	 if (type & Color)
	 {
	    const char* cptr = ptr + offset(Color);
	    glColorPointer(4, GL_FLOAT, stride, cptr);
	    glEnableClientState(GL_COLOR_ARRAY);
	 }
	 if (type & SecColor)
	 {
	    const char* scptr = ptr + offset(SecColor);
	    glSecondaryColorPointerEXT(4, GL_FLOAT, stride, (GLvoid*)(scptr));
	    glEnableClientState(GL_SECONDARY_COLOR_ARRAY_EXT);
	 }
	 if (type & FogCoord)
	 {
	    const char* fptr = ptr + offset(FogCoord);
	    glFogCoordPointerEXT(GL_FLOAT, stride, fptr);
	    glEnableClientState(GL_FOG_COORDINATE_ARRAY_EXT);

	 }
	 const u8 texCoordCount = (type & TexCoordMask) >> TexCoordShift;
	 for (u8 i = 0; i < texCoordCount; i++)
	 {
	    const char* tptr = ptr + offset((Type)((i + 1) << TexCoordShift));
	    glActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glClientActiveTextureARB(GL_TEXTURE0_ARB + i);
	    glTexCoordPointer(2, GL_FLOAT, stride, tptr);

	 }
	 if (texCoordCount > 0)
	 {
	    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	 }
	 /*if (type & TexCoord) {
	    const u8 typeTexCoordCount = type & TexCoordMask
	    glTexCoordPointer(2, GL_FLOAT, stride, tptr);
	    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	    }*/

      }


      void VertexBuffer::unbind()
      {
	 glDisableClientState(GL_VERTEX_ARRAY);
	 glDisableClientState(GL_NORMAL_ARRAY);
	 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	 glDisableClientState(GL_FOG_COORDINATE_ARRAY_EXT);
	 glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
	 glDisableClientState(GL_COLOR_ARRAY);
      }

      void VertexBuffer::draw(GLenum primitiveType, u32 count, u32 off)
      {
	 if (count == 0){
	    count = size;
	 }

	 //bind();
	 glDrawArrays(primitiveType, off, count);
	 //unbind();

      }

      void VertexBuffer::drawIndexed(const u32* idx, GLenum primitiveType, u32 count, u32 off)
      {
	 throw BaseException("Method VertexBuffer::drawIndexed is not implemented");
      }

   }

}
